package com.hongliang.nio_c1.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import static com.hongliang.nio_c1.utils.ByteBufferUtil.debugAll;

/**
 * @author Hongliang Zhu
 * @create 2021-08-22 19:40
 */
@Slf4j
public class ServerSelector {

    public static void main(String[] args) throws IOException {
        //  创建Secector ,管理多个channel
        Selector selector = Selector.open();

        ServerSocketChannel server = ServerSocketChannel.open();

        server.configureBlocking(false);

        // 注册到selector 建立和channel的联系
        // SelectionKey 就是将来事件发生后，通过它可以知道事件和哪个Channel的事件
        SelectionKey sccKey = server.register(selector, 0, null);
        log.debug("register key {}==>", sccKey);
        sccKey.interestOps(SelectionKey.OP_ACCEPT);

        server.bind(new InetSocketAddress(8080));

        // 3. 循环接受客户端连接请求
        while (true){
            // select 方法， 没有事件发生时，阻塞，有事件，线程才会恢复运行。
            // selector在事件未处理时， 不会阻塞。事件发生后要么处理，要么取消，不能置之不理、
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                // 下面这行代码很重要！！！！！！！！！！！！！！！处理完一定要删除！！！
                ///////////////////////////////////////
                iterator.remove();
                //////////////////////////////////////
                log.debug("key: {}", key);

                // 区分事件类型
                if (key.isAcceptable()) {
                    // accept事件
                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    ByteBuffer buffer = ByteBuffer.allocate(16);
                    // 将ByteBuffer作为附件关联到selectKey上
                    SelectionKey scKey = sc.register(selector, 0, buffer);
                    scKey.interestOps(SelectionKey.OP_READ);
                    log.debug("{}", sc);
                }else if(key.isReadable()){
                    try {
                        // read 事件
                        SocketChannel channel = (SocketChannel)key.channel();
                        // 获取selectKey 上关联的附件
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        int read = channel.read(buffer); // 正常断开， read 为-1
                        if(read == -1){
                            key.cancel();
                        }else {
                            // 处理消息边界
                            split(buffer);
                            if (buffer.position() == buffer.limit()){
                                ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity() * 2);
                                buffer.flip();
                                newBuffer.put(buffer);
                                // 重新attach
                                key.attach(newBuffer);
                            }
//                            buffer.flip();
//                            System.out.println(Charset.defaultCharset().decode(buffer));
//
////                            debugRead(buffer);
//                            buffer.clear();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        // 客户端断开后会触发read事件, 需要将key取消（从selector的keys集合中真正删除 key）
                        key.cancel(); // 事件要么处理，要么取消
                    }
                }

            }
        }

    }

    private static void split(ByteBuffer source) {
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            if (source.get(i) == '\n') {
                int length = i + 1 - source.position();
                ByteBuffer buf = ByteBuffer.allocate(length);
                // 从source 读， 写到buf中
                for (int j = 0; j < length; j++) {
                    buf.put(source.get());
                }
                debugAll(buf);
            }
        }
        source.compact();
    }
}
